package club.banyuan.reserve.config;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * JWT 工具类
 */
@Configuration
public class JwtUtils {

    private final static String REDIS_JWT_TOKEN_PREFIX_KEY = "banyuan:sec:jwt:";

    private final static String KEY = "banyuansec";
    private final static int TTL = 86400000; // 1天
    private final static int REMEMBER_ME = 604800000; // 记住我，7天

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 创建JWT
     *
     * @param rememberMe  记住我
     * @param id          用户id
     * @param subject     用户名
     * @param roles       用户角色
     * @param authorities 用户权限
     * @return JWT
     */
    public String createJWT(Boolean rememberMe, Integer id, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) {
        Date now = new Date();
        JwtBuilder builder = Jwts.builder()
                .setId(id.toString())
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, KEY)
                .claim("roles", roles)
                .claim("authorities", authorities);

        // 设置过期时间
        int ttl = rememberMe ? REMEMBER_ME : TTL;
        builder.setExpiration(DateUtil.offsetMillisecond(now, ttl));

        String jwt = builder.compact();

        // 将生成的JWT保存至Redis
        stringRedisTemplate.opsForValue().set(REDIS_JWT_TOKEN_PREFIX_KEY + subject, jwt, ttl, TimeUnit.MILLISECONDS);
        return jwt;
    }

    /**
     * 创建JWT
     *
     * @param authentication 用户认证信息
     * @param rememberMe     记住我
     * @return JWT
     */
    public String createJWT(Authentication authentication, Boolean rememberMe) {
        MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal();
        return createJWT(rememberMe, userDetails.getId(), userDetails.getUsername(), userDetails.getRoles(), userDetails.getAuthorities());
    }

    /**
     * 解析JWT
     *
     * @param jwt JWT
     * @return {@link Claims}
     */
    public Claims parseJWT(String jwt) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(KEY)
                    .parseClaimsJws(jwt)
                    .getBody();

            String username = claims.getSubject();
            String redisKey = REDIS_JWT_TOKEN_PREFIX_KEY + username;

            // 校验redis中的JWT是否存在
            Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
            if (Objects.isNull(expire) || expire <= 0) {
                throw new SecurityException("Token 已过期，请重新登录！");
            }

            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期
            String redisToken = stringRedisTemplate.opsForValue().get(redisKey);
            if (!StrUtil.equals(jwt, redisToken)) {
                throw new SecurityException("当前用户已在别处登录，请尝试更改密码或重新登录！");
            }
            return claims;
        } catch (ExpiredJwtException e) {
            throw new SecurityException("Token 已过期");
        } catch (UnsupportedJwtException e) {
            throw new SecurityException("不支持的 Token");
        } catch (MalformedJwtException e) {
            throw new SecurityException("Token 无效");
        } catch (SignatureException e) {
            throw new SecurityException("无效的 Token 签名");
        } catch (IllegalArgumentException e) {
            throw new SecurityException("Token 参数不存在");
        }
    }

    /**
     * 设置JWT过期
     *
     * @param request 请求
     */
    public void invalidateJWT(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        String username = getUsernameFromJWT(jwt);
        // 从redis中清除JWT
        stringRedisTemplate.delete(REDIS_JWT_TOKEN_PREFIX_KEY + username);
    }

    /**
     * 根据 jwt 获取用户名
     *
     * @param jwt JWT
     * @return 用户名
     */
    public String getUsernameFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return claims.getSubject();
    }

    /**
     * 从 request 的 header 中获取 JWT
     *
     * @param request 请求
     * @return JWT
     */
    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}
